home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 2.toast / pc / sample code / quicktime / effects / makeeffectslideshow / makeeffectslideshow.c < prev    next >
Encoding:
Text File  |  2000-09-28  |  22.7 KB  |  699 lines

  1. //////////
  2. //
  3. //    File:        MakeEffectSlideShow.c
  4. //
  5. //    Contains:    QuickTime video effect support for QuickTime movies and still images.
  6. //
  7. //    Written by:    Tim Monroe
  8. //                Based (heavily!) on the MakeEffectSlideShow code written by Sam Bushell.
  9. //
  10. //    Copyright:    © 1997-1999 by Apple Computer, Inc., all rights reserved.
  11. //
  12. //    Change History (most recent first):
  13. //
  14. //       <7>         03/03/99    rtm        added support for MakeImageDescriptionForEffect (QT 4.0 and later)
  15. //       <6>         03/12/98    rtm        added global flag gDoneWithDialog to fix Windows dialog box problems
  16. //       <5>         02/28/98    rtm        revised event/message handling following QTShowEffect.c
  17. //       <4>         08/01/98    rtm        sync'ed with latest code from Sam: reworked QTEffects_GetFirstVideoTrackInMovie
  18. //                                    to use GetMovieIndTrackType
  19. //       <3>         11/24/97    rtm        reworked event-loop processing: changed QTEffects_DoMakeEffectSlideShowMovieForFiles into
  20. //                                    QTEffects_DisplayDialogForSources and QTEffects_RespondToDialogSelection
  21. //       <2>         11/21/97    rtm        further work
  22. //       <1>         11/06/97    rtm        first file; integrated existing code with shell framework;
  23. //                                    added endian macros where appropriate
  24. //       
  25. //    This application takes the video tracks from two or more movies, asks 
  26. //    the user to select a 2-source effect, and makes a slide show movie 
  27. //    which uses the effect to switch from one video track to the next. 
  28. //    
  29. //    Short video tracks are scaled up to a minimum length, and we can import
  30. //    all sorts of graphical image formats as movies, so you can use this to 
  31. //    generate a slide show movie from still images -- or even a collection
  32. //    of stills and movies.
  33. //    
  34. //    Try it out: drag a bunch of JPEG files of the same size onto the application icon.
  35. //    Alternatively, launch the application and then choose them from the Test... menu.
  36. //
  37. //////////
  38.  
  39. // header files
  40. #include "MakeEffectSlideShow.h"
  41.  
  42. // global variables
  43. QTParameterDialog            gEffectsDialog = 0L;
  44. QTAtomContainer                gEffectSample = 0;                // effects sample
  45. QTAtomContainer                gEffectList = 0;
  46. PicHandle                    gPosterA = NULL;
  47. PicHandle                    gPosterB = NULL;
  48. Movie                        gSrcMovies[kMaxNumSources];
  49. Track                        gSrcTracks[kMaxNumSources];
  50. UInt16                        gSpecCount = 0;        
  51. FSSpec                        gSpecList[kMaxNumSources];
  52. Boolean                        gDoneWithDialog = false;        // are we done using the effects parameters dialog box?
  53.  
  54.  
  55. //////////
  56. //
  57. // QTEffects_GetFirstVideoTrackInMovie
  58. // Return, through the theTrack parameter, the first video track in the specified movie.
  59. //
  60. // Actually, we look for the first track that has the kCharacteristicCanSendVideo characteristic,
  61. // so we can apply effects to MPEG or QD3D tracks as well.
  62. //
  63. // If no such track is found, return invalidTrack as the function result.
  64. //
  65. //////////
  66.  
  67. OSErr QTEffects_GetFirstVideoTrackInMovie (Movie theMovie, Track *theTrack)
  68. {
  69.     *theTrack = GetMovieIndTrackType(theMovie, 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
  70.     
  71.     if (*theTrack == NULL)
  72.         return(invalidTrack);
  73.         
  74.     return(noErr);
  75. }
  76.  
  77.  
  78. //////////
  79. //
  80. // QTEffects_GetPosterPictFromFirstVideoTrackInMovieFile
  81. // Get the poster picture for the first video track in the specified movie file.
  82. //
  83. //////////
  84.  
  85. PicHandle QTEffects_GetPosterPictFromFirstVideoTrackInMovieFile (FSSpec *theSpec)
  86. {
  87.     PicHandle    myPict = NULL;
  88.     Movie        myMovie = NULL;
  89.     Track        myTrack = NULL;
  90.     short        myRefNum = 0;
  91.     OSErr        myErr = noErr;
  92.     
  93.     myErr = OpenMovieFile(theSpec, &myRefNum, fsRdPerm);
  94.     BailError(myErr);
  95.  
  96.     myErr = NewMovieFromFile(&myMovie, myRefNum, NULL, NULL, 0, NULL);
  97.     BailError(myErr);
  98.     
  99.     SetMoviePlayHints(myMovie, hintsHighQuality, hintsHighQuality);
  100.  
  101.     myErr = CloseMovieFile(myRefNum);
  102.     BailError(myErr);
  103.     
  104.     myErr = QTEffects_GetFirstVideoTrackInMovie(myMovie, &myTrack);
  105.     BailNil(myTrack);
  106.     
  107.     myPict = GetTrackPict(myTrack, GetMoviePosterTime(myMovie));
  108.  
  109. bail:
  110.     if (myMovie != NULL)
  111.         DisposeMovie(myMovie);
  112.     
  113.     return(myPict);
  114. }
  115.  
  116.  
  117. //////////
  118. //
  119. // QTEffects_CopyPortionOfTrackToTrack
  120. // Extract a portion of the source track and copy it to the destination track.
  121. //
  122. // The parameter theSourcePortions is formed by OR-ing any of the portion constants.
  123. // For example, to copy the start and middle portions, set theSourcePortions to (eStartPortion | eMiddlePortion).
  124. //
  125. // We assume that the track time scale is kTimeScale; we also assume that the track has been scaled
  126. // so that it is at least kMinimumDuration long.
  127. //
  128. //////////
  129.  
  130. OSErr QTEffects_CopyPortionOfTrackToTrack (Track theSourceTrack, UInt16 theSourcePortions, Track theDestTrack, TimeValue theDestStartTime, TimeValue *theDestDuration)
  131. {
  132.     TimeValue    mySourceTrackDuration;
  133.     TimeValue    mySourceSegmentStart, mySourceSegmentEnd, mySourceSegmentDuration;
  134.     OSErr        myErr = noErr;
  135.     
  136.     mySourceTrackDuration = GetTrackDuration(theSourceTrack);
  137.     
  138.     if (theSourcePortions & eStartPortion)
  139.         mySourceSegmentStart = 0;
  140.     else if (theSourcePortions & eMiddlePortion)
  141.         mySourceSegmentStart = kEffectDuration;
  142.     else
  143.         mySourceSegmentStart = mySourceTrackDuration - kEffectDuration;
  144.     
  145.     if (theSourcePortions & eFinishPortion)
  146.         mySourceSegmentEnd = mySourceTrackDuration;
  147.     else if (theSourcePortions & eMiddlePortion)
  148.         mySourceSegmentEnd = mySourceTrackDuration - kEffectDuration;
  149.     else
  150.         mySourceSegmentEnd = kEffectDuration;
  151.     
  152.     mySourceSegmentDuration = mySourceSegmentEnd - mySourceSegmentStart;
  153.     
  154.     myErr = InsertTrackSegment( theSourceTrack,
  155.                                 theDestTrack,
  156.                                 mySourceSegmentStart,
  157.                                 mySourceSegmentDuration,
  158.                                 theDestStartTime);
  159.     
  160.     *theDestDuration = mySourceSegmentDuration;
  161.     
  162.     return(myErr);
  163. }
  164.  
  165.  
  166. //////////
  167. //
  168. // QTEffects_DisplayDialogForSources
  169. // Display the standard effects parameters dialog box for the movies passed in.
  170. //
  171. //////////
  172.  
  173. OSErr QTEffects_DisplayDialogForSources (FSSpec *theSpecList, UInt16 theSpecCount)
  174. {
  175.     OSErr                    myErr = noErr;
  176.     
  177.     // make sure that there are enough sources: you can't make an omelette without enough eggs
  178.     if (theSpecCount < 2) {
  179.         myErr = paramErr;
  180.         goto bail;
  181.     }
  182.  
  183.     // assign source count to a global, so QTEffects_RespondToDialogSelection has access to it
  184.     gSpecCount = theSpecCount;        
  185.     
  186.     // get a poster frame for the first two movies
  187.     if (theSpecCount >= 1)
  188.         gPosterA = QTEffects_GetPosterPictFromFirstVideoTrackInMovieFile(&theSpecList[0]);
  189.     
  190.     if (theSpecCount >= 2)
  191.         gPosterB = QTEffects_GetPosterPictFromFirstVideoTrackInMovieFile(&theSpecList[1]);
  192.     
  193.     // ask the user to select a two-source effect
  194.  
  195.     myErr = QTNewAtomContainer(&gEffectSample);
  196.     BailError(myErr);
  197.     
  198.     myErr = QTGetEffectsList(&gEffectList, 2, 2, 0);        // min == max == 2
  199.     BailError(myErr);
  200.     
  201.     myErr = QTCreateStandardParameterDialog(gEffectList, gEffectSample, 0, &gEffectsDialog);
  202.     BailError(myErr);
  203.     
  204.     // insert poster frames into dialog
  205.     if (gPosterA != NULL) {
  206.         QTParamPreviewRecord            pr;
  207.  
  208.         pr.sourcePicture = gPosterA;
  209.         pr.sourceID = 1;
  210.         QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &pr);
  211.     }
  212.  
  213.     if (gPosterB != NULL) {
  214.         QTParamPreviewRecord            pr;
  215.  
  216.         pr.sourcePicture = gPosterB;
  217.         pr.sourceID = 2;
  218.         QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &pr);
  219.     }
  220.     
  221.     // now, the frontmost window is the standard effects parameter dialog box;
  222.     // on the Mac, we call QTEffects_HandleEffectsDialogEvents in our main event loop
  223.     // to find and process events targeted at the effects parameter dialog box; on Windows,
  224.     // we need to use a different strategy: we install a modeless dialog callback procedure
  225.     // that is called internally by QTML
  226.  
  227. #if TARGET_OS_WIN32
  228.     gDoneWithDialog = false;
  229.     
  230.     // force the dialog box to be drawn
  231.     {
  232.         EventRecord            myEvent = {0};
  233.         
  234.         QTEffects_EffectsDialogCallback(&myEvent, FrontWindow(), 0);
  235.     }
  236.     
  237.     SetModelessDialogCallbackProc(FrontWindow(), (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);
  238.     QTMLSetWindowWndProc(FrontWindow(), QTEffects_CustomDialogWndProc);
  239. #endif
  240.     
  241. bail:
  242.     return(myErr);
  243. }
  244.  
  245.  
  246. //////////
  247. //
  248. // QTEffects_RespondToDialogSelection
  249. // If theErr is codecParameterDialogConfirm, make an effects movie.
  250. // If theErr is userCanceledErr, do any necessary clean up.
  251. //
  252. // Construct a movie that links a sequence of image or movie files together with real-time effects.
  253. // That is, show the first image or movie, transition (fade or whatever) to the second, show the second,
  254. // transition to the third, and so forth.
  255. //
  256. // We use NewMovieFromFile, which will use movie importers or graphics importers as appropriate,
  257. // so each file we open may end up as either.
  258. //
  259. // If a movie has only one sample in its first video track, we assume it's a still image,
  260. // and use several copies of that sample with different lengths. This is flawed, since the sample
  261. // could be a tweened 3DMF movie.
  262. //
  263. //////////
  264.  
  265. void QTEffects_RespondToDialogSelection (OSErr theErr)
  266. {
  267.     Boolean                    myDialogWasCancelled = false;
  268.     StandardFileReply        myReply;
  269.     short                    myResID = movieInDataForkResID;
  270.     UInt16                    myMovieIter;
  271.     short                    mySrcMovieRefNum = 0;
  272.     Movie                    myPrevSrcMovie = NULL;
  273.     Track                    myPrevSrcTrack = NULL;
  274.     Movie                    myNextSrcMovie = NULL;
  275.     Track                    myNextSrcTrack = NULL;
  276.     short                    myDestMovieRefNum = 0;
  277.     Movie                    myDestMovie = NULL;
  278.     Fixed                    myDestMovieWidth, myDestMovieHeight;
  279.     ImageDescriptionHandle    myDesc = NULL;
  280.     Track                    videoTrackFX, videoTrackA, videoTrackB;
  281.     Media                    videoMediaFX, videoMediaA, videoMediaB;
  282.     TimeValue                myCurrentDuration = 0;
  283.     TimeValue                myReturnedDuration;
  284.     Boolean                    isFirstTransition = true;
  285.     TimeValue                myMediaTransitionDuration;
  286.     TimeValue                myMediaFXStartTime, myMediaFXDuration;
  287.     OSType                    myEffectCode;
  288.     long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  289.     long                    myLong;
  290.     OSErr                    myErr = noErr;
  291.  
  292.     // standard parameter box has been dismissed, so remember that fact
  293.     gEffectsDialog = 0L;
  294.     
  295.     myDialogWasCancelled = (theErr == userCanceledErr);
  296.     
  297.     // we're finished with the effect list and movie posters    
  298.     QTDisposeAtomContainer(gEffectList);
  299.     
  300.     if (gPosterA != NULL)
  301.         KillPicture(gPosterA);
  302.         
  303.     if (gPosterB != NULL)
  304.         KillPicture(gPosterB);
  305.     
  306.     // when the sign says stop, then stop
  307.     if (myDialogWasCancelled)
  308.         goto bail;
  309.  
  310.     // add atoms naming the sources to gEffectSample
  311.     myLong = EndianU32_NtoB(kSourceOneName);
  312.     QTInsertChild(gEffectSample, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myLong), &myLong, NULL);
  313.  
  314.     myLong = EndianU32_NtoB(kSourceTwoName);
  315.     QTInsertChild(gEffectSample, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myLong), &myLong, NULL);
  316.     
  317.     // extract the 'what' atom to find out what kind of effect it is
  318.     {
  319.         QTAtom            myEffectAtom;
  320.         QTAtomID        myEffectAtomID;
  321.         long            myEffectCodeSize;
  322.         Ptr                myEffectCodePtr;
  323.  
  324.         myEffectAtom = QTFindChildByIndex(gEffectSample, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, &myEffectAtomID);
  325.         
  326.         myErr = QTLockContainer(gEffectSample);
  327.         BailError(myErr);
  328.  
  329.         myErr = QTGetAtomDataPtr(gEffectSample, myEffectAtom, &myEffectCodeSize, &myEffectCodePtr);
  330.         BailError(myErr);
  331.  
  332.         if (myEffectCodeSize != sizeof(OSType)) {
  333.             myErr = paramErr;
  334.             goto bail;
  335.         }
  336.         
  337.         myEffectCode = *(OSType *)myEffectCodePtr;        // "tsk"
  338.         myEffectCode = EndianU32_BtoN(myEffectCode);    // because the data is read from an atom container
  339.         
  340.         myErr = QTUnlockContainer(gEffectSample);
  341.         BailError(myErr);
  342.     }
  343.  
  344.     // ask the user for the name of the new movie file
  345.     StandardPutFile("\pSave effect movie file as:", "\pUntitled.mov", &myReply);
  346.     if (!myReply.sfGood)
  347.         goto bail;                // deal with user cancelling
  348.  
  349.     // create a movie file for the destination movie
  350.     myErr = CreateMovieFile(&myReply.sfFile, FOUR_CHAR_CODE('TVOD'), 0, myFlags, &myDestMovieRefNum, &myDestMovie);
  351.     BailError(myErr);
  352.     
  353.     // open the first file as a movie; call the first movie myPrevSrcMovie
  354.     myErr = OpenMovieFile(&gSpecList[0], &mySrcMovieRefNum, fsRdPerm);
  355.     BailError(myErr);
  356.     
  357.     myErr = NewMovieFromFile(&myPrevSrcMovie, mySrcMovieRefNum, NULL, NULL, 0, NULL);
  358.     BailError(myErr);
  359.     
  360.     myErr = CloseMovieFile(mySrcMovieRefNum);
  361.     BailError(myErr);
  362.     
  363.     // if the movie is shorter than kMinimumDuration, scale it to that length
  364.     SetMovieTimeScale(myPrevSrcMovie, kTimeScale);
  365.     myErr = QTEffects_GetFirstVideoTrackInMovie(myPrevSrcMovie, &myPrevSrcTrack);
  366.     BailNil(myPrevSrcTrack);
  367.     
  368.     if (GetTrackDuration(myPrevSrcTrack) < kMinimumDuration) {
  369.         myErr = ScaleTrackSegment(myPrevSrcTrack, 0, GetTrackDuration(myPrevSrcTrack), kMinimumDuration);
  370.         BailError(myErr);
  371.     }
  372.     
  373.     // find out how big the first movie is; we'll use it as the size of all our tracks
  374.     GetTrackDimensions(myPrevSrcTrack, &myDestMovieWidth, &myDestMovieHeight);
  375.     
  376. #if USES_MAKE_IMAGE_DESC_FOR_EFFECT
  377.     // create a new sample description for the effect,
  378.     // which is just an image description specifying the effect and its dimensions
  379.     myErr = MakeImageDescriptionForEffect(myEffectCode, &myDesc);
  380.     if (myErr != noErr)
  381.         BailError(myErr);
  382. #else
  383.     // create a new sample description for the effect,
  384.     // which is just an image description specifying the effect and its dimensions
  385.     myDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
  386.     BailNil(myDesc);
  387.     
  388.     (**myDesc).idSize = sizeof(ImageDescription);
  389.     (**myDesc).cType = myEffectCode;
  390.     (**myDesc).hRes = 72L << 16;
  391.     (**myDesc).vRes = 72L << 16;
  392.     (**myDesc).dataSize = 0L;
  393.     (**myDesc).frameCount = 1;
  394.     (**myDesc).depth = 0;
  395.     (**myDesc).clutID = -1;
  396. #endif
  397.     
  398.     // fill in the fields of the sample description
  399.     (**myDesc).vendor = kAppleManufacturer;
  400.     (**myDesc).temporalQuality = codecNormalQuality;
  401.     (**myDesc).spatialQuality = codecNormalQuality;
  402.     (**myDesc).width = FixRound(myDestMovieWidth);
  403.     (**myDesc).height = FixRound(myDestMovieHeight);
  404.  
  405.     // add three video tracks to the destination movie:
  406.     //     - videoTrackFX is where the effects and stills live; it's user-visible.
  407.     //    - videoTrackA is where the "source A"s for effects live; it's hidden by the input map
  408.     //    - videoTrackB is where the "source B"s for effects live; it's hidden by the input map
  409.     videoTrackFX = NewMovieTrack(myDestMovie, myDestMovieWidth, myDestMovieHeight, 0);
  410.     BailNil(videoTrackFX);
  411.     videoMediaFX = NewTrackMedia(videoTrackFX, VideoMediaType, kTimeScale, NULL, 0);
  412.     BailNil(videoMediaFX);
  413.     myErr = BeginMediaEdits(videoMediaFX);
  414.     BailError(myErr);
  415.     
  416.     videoTrackA = NewMovieTrack(myDestMovie, myDestMovieWidth, myDestMovieHeight, 0);
  417.     BailNil(videoTrackA);
  418.     videoMediaA = NewTrackMedia(videoTrackA, VideoMediaType, kTimeScale, NULL, 0);
  419.     BailNil(videoMediaA);
  420.  
  421.     videoTrackB = NewMovieTrack(myDestMovie, myDestMovieWidth, myDestMovieHeight, 0);
  422.     BailNil(videoTrackB);
  423.     videoMediaB = NewTrackMedia(videoTrackB, VideoMediaType, kTimeScale, NULL, 0);
  424.     BailNil(videoMediaB);
  425.  
  426.     // create the input map
  427.     {
  428.         long                myRefIndex1, myRefIndex2;
  429.         QTAtomContainer        myInputMap;
  430.         QTAtom                myInputAtom;
  431.         OSType                myInputType;
  432.  
  433.         QTNewAtomContainer(&myInputMap);
  434.  
  435.         // first input
  436.         if (videoTrackA) {
  437.         
  438.             AddTrackReference(videoTrackFX, videoTrackA, kTrackModifierReference, &myRefIndex1);
  439.             QTInsertChild(myInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex1, 0, 0, NULL, &myInputAtom);
  440.     
  441.             myInputType = EndianU32_NtoB(kTrackModifierTypeImage);
  442.             QTInsertChild(myInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myInputType), &myInputType, NULL);
  443.     
  444.             myLong = EndianU32_NtoB(kSourceOneName);
  445.             QTInsertChild(myInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myLong), &myLong, NULL);
  446.         }
  447.  
  448.         // second input
  449.         if (videoTrackB) {
  450.         
  451.             AddTrackReference(videoTrackFX, videoTrackB, kTrackModifierReference, &myRefIndex2);
  452.             QTInsertChild(myInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex2, 0, 0, NULL, &myInputAtom);
  453.     
  454.             myInputType = EndianU32_NtoB(kTrackModifierTypeImage);
  455.             QTInsertChild(myInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myInputType), &myInputType, NULL);
  456.     
  457.             myLong = EndianU32_NtoB(kSourceTwoName);
  458.             QTInsertChild(myInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myLong), &myLong, NULL);
  459.         }
  460.  
  461.         // set that map
  462.         SetMediaInputMap(GetTrackMedia(videoTrackFX), myInputMap);
  463.         
  464.         QTDisposeAtomContainer(myInputMap);
  465.     }
  466.  
  467.     myCurrentDuration = 0;
  468.  
  469. #if MAKE_STILL_SECTIONS
  470.     // copy the first sample of the first video track of the first movie to videoTrackFX, with duration kStillDuration.
  471.     myErr = CopyPortionOfTrackToTrack(myPrevSrcTrack, eStartPortion + eMiddlePortion, videoTrackFX, myCurrentDuration, &myReturnedDuration);
  472.     BailError(myErr);
  473.     
  474.     myCurrentDuration += myReturnedDuration;
  475. #endif 
  476.  
  477.     // now process any remaining files
  478.     myMovieIter = 1;
  479.     while (myMovieIter < gSpecCount) {
  480.         
  481.         // open the next file as a movie; call it nextSourceMovie
  482.         myErr = OpenMovieFile(&gSpecList[myMovieIter], &mySrcMovieRefNum, fsRdPerm);
  483.         BailError(myErr);
  484.         
  485.         myErr = NewMovieFromFile(&myNextSrcMovie, mySrcMovieRefNum, NULL, NULL, 0, NULL);
  486.         BailError(myErr);
  487.         
  488.         // we're done with the movie file, so close it
  489.         myErr = CloseMovieFile(mySrcMovieRefNum);
  490.         BailError(myErr);
  491.         
  492.         // if the movie is shorter than kMinimumDuration, scale it to that length
  493.         SetMovieTimeScale(myNextSrcMovie, kTimeScale);
  494.         myErr = QTEffects_GetFirstVideoTrackInMovie(myNextSrcMovie, &myNextSrcTrack);
  495.         BailNil(myNextSrcTrack);
  496.         
  497.         if (GetTrackDuration(myNextSrcTrack) < kMinimumDuration) {
  498.             myErr = ScaleTrackSegment(myNextSrcTrack, 0, GetTrackDuration(myNextSrcTrack), kMinimumDuration);
  499.             BailError(myErr);
  500.         }
  501.  
  502.         // create a transition effect from the previous source movie's first video sample to the next source movie's first video sample
  503.         // (the effect should have duration kEffectDuration);
  504.         // this involves adding one sample to each of the three video tracks:
  505.         
  506.         //    sample from previous source movie     -> videoTrackA
  507.         myErr = QTEffects_CopyPortionOfTrackToTrack(myPrevSrcTrack, eFinishPortion, videoTrackA, myCurrentDuration, &myReturnedDuration);
  508.         BailError(myErr);
  509.         
  510.         //    sample from next source movie         -> videoTrackB
  511.         myErr = QTEffects_CopyPortionOfTrackToTrack(myNextSrcTrack, eStartPortion, videoTrackB, myCurrentDuration, &myReturnedDuration);
  512.         BailError(myErr);
  513.         
  514.         //    effect sample                       -> videoTrackFX
  515.         if (isFirstTransition) {
  516.             myMediaTransitionDuration = myReturnedDuration;
  517.             myMediaFXStartTime = GetMediaDuration(videoMediaFX);
  518.             myErr = AddMediaSample(videoMediaFX, gEffectSample, 0, GetHandleSize(gEffectSample), myMediaTransitionDuration, (SampleDescriptionHandle)myDesc, 1, 0, NULL);
  519.             BailError(myErr);
  520.             
  521.             myMediaFXDuration = GetMediaDuration(videoMediaFX) - myMediaFXStartTime;
  522.             isFirstTransition = false;
  523.         }
  524.         
  525.         myErr = InsertMediaIntoTrack(videoTrackFX, myCurrentDuration, myMediaFXStartTime, myMediaFXDuration, FixRatio(myReturnedDuration, myMediaTransitionDuration));
  526.         BailError(myErr);
  527.         
  528.         myCurrentDuration += myReturnedDuration;
  529.         
  530. #if MAKE_STILL_SECTIONS
  531.         // copy the first video sample of myNextSrcMovie to videoTrackFX, with duration kStillDuration.
  532.         myErr = QTEffects_CopyPortionOfTrackToTrack(myNextSrcTrack, eMiddlePortion + (myMovieIter + 1 == theSpecCount) ? eFinishPortion : 0, videoTrackFX, myCurrentDuration, &myReturnedDuration);
  533.         BailError(myErr);
  534.         
  535.         myCurrentDuration += myReturnedDuration;
  536. #endif // MAKE_STILL_SECTIONS
  537.         
  538.         // dispose of previous source movie.  
  539.         DisposeMovie(myPrevSrcMovie);
  540.         
  541.         myPrevSrcMovie = myNextSrcMovie;
  542.         myPrevSrcTrack = myNextSrcTrack;
  543.         myNextSrcMovie = NULL;
  544.         myNextSrcTrack = NULL;
  545.         
  546.         myMovieIter++;
  547.     } // while
  548.     
  549.     myErr = EndMediaEdits(videoMediaFX);
  550.     BailError(myErr);
  551.  
  552.     myErr = AddMovieResource(myDestMovie, myDestMovieRefNum, &myResID, "\pMovie 1");
  553.     BailError(myErr);
  554.     
  555.     CloseMovieFile(myDestMovieRefNum);
  556.     
  557.     if (myPrevSrcMovie != NULL)
  558.         DisposeMovie(myPrevSrcMovie);
  559.         
  560.     DisposeMovie(myDestMovie);
  561.     
  562. bail:
  563.     QTDisposeAtomContainer(gEffectSample);
  564.     DisposeHandle((Handle)myDesc);
  565.  
  566.     return;
  567. }
  568.  
  569.  
  570. #if TARGET_OS_WIN32
  571. //////////
  572. //
  573. // QTEffects_EffectsDialogCallback
  574. // This function is called by QTML when it processes events for the standard or custom effects parameter dialog box.
  575. // 
  576. //////////
  577.  
  578. static void QTEffects_EffectsDialogCallback (EventRecord *theEvent, DialogRef theDialog, DialogItemIndex theItemHit)
  579. {
  580.     QTParamDialogEventRecord    myRecord;
  581.  
  582.     myRecord.theEvent = theEvent;
  583.     myRecord.whichDialog = theDialog;
  584.     myRecord.itemHit = theItemHit;
  585.  
  586.     // 
  587.     QTStandardParameterDialogDoAction(gEffectsDialog, pdActionModelessCallback, &myRecord);
  588.     
  589.     // see if the event is meant for the effects parameter dialog box
  590.     QTEffects_HandleEffectsDialogEvents(theEvent, theItemHit);
  591. }
  592.  
  593.  
  594. //////////
  595. //
  596. // QTEffects_CustomDialogWndProc
  597. // Handle messages for the custom effects parameters dialog box.
  598. // 
  599. //////////
  600.  
  601. LRESULT CALLBACK QTEffects_CustomDialogWndProc (HWND theWnd, UINT theMessage, UINT wParam, LONG lParam)
  602. {
  603.     EventRecord            myEvent = {0};
  604.     
  605.     if (!gDoneWithDialog && (theMessage == 0x7FFF))
  606.         QTEffects_EffectsDialogCallback(&myEvent, GetNativeWindowPort(theWnd), 0);
  607.  
  608.     return(DefWindowProc(theWnd, theMessage, wParam, lParam));
  609. }
  610. #endif
  611.  
  612.  
  613. //////////
  614. //
  615. // QTEffects_HandleEffectsDialogEvents
  616. // Process events that might be targeted at the standard effects parameter dialog box.
  617. // Return true if the event was completely handled.
  618. // 
  619. //////////
  620.  
  621. Boolean QTEffects_HandleEffectsDialogEvents (EventRecord *theEvent, DialogItemIndex theItemHit)
  622. {
  623.     Boolean            isHandled = false;
  624.     OSErr            myErr = noErr;
  625.     
  626.     // pass the event to the standard effects parameter dialog box handler
  627.     myErr = QTIsStandardParameterDialogEvent(theEvent, gEffectsDialog);
  628.     
  629.     // the result from QTIsStandardParameterDialogEvent tells us how to respond next
  630.     switch (myErr) {
  631.         
  632.         case codecParameterDialogConfirm:
  633.         case userCanceledErr:
  634.             // the user clicked the OK or Cancel button; dismiss the dialog box and respond accordingly
  635.             gDoneWithDialog = true;
  636.             QTDismissStandardParameterDialog(gEffectsDialog);
  637.             QTEffects_RespondToDialogSelection(myErr);
  638.             gEffectsDialog = 0L;
  639.             isHandled = true;
  640.             break;
  641.             
  642.         case noErr:
  643.             // the event was completely handled by QTIsStandardParameterDialogEvent
  644.             isHandled = true;
  645.             break;
  646.             
  647.         case featureUnsupported:
  648.             // the event was not handled by QTIsStandardParameterDialogEvent;
  649.             // let the event be processed normally
  650.             isHandled = false;
  651.             break;
  652.             
  653.         default:
  654.             // the event was not handled by QTIsStandardParameterDialogEvent;
  655.             // do not let the event be processed normally
  656.             isHandled = true;
  657.             break;
  658.     }
  659.  
  660.     return(isHandled);
  661. }
  662.  
  663.  
  664. //////////
  665. //
  666. // QTEffects_PromptUserForFilesAndMakeEffect
  667. // Let the user select some movies, then apply the effect to them.
  668. //
  669. // If the user cancels the first file-open dialog box, there are zero sources.
  670. // If the user cancels the second file-open dialog box, there is one source.
  671. // 
  672. //////////
  673.  
  674. void QTEffects_PromptUserForFilesAndMakeEffect (void)
  675. {
  676.     int            mySpecCount = 0;
  677.     
  678.     // ask for up to kMaxNumSources movie files;
  679.     // accept early cancels; they just mean there are fewer input movies
  680.     mySpecCount = 0;
  681.     while (mySpecCount < kMaxNumSources) {
  682.         SFTypeList                myTypeList;
  683.         StandardFileReply        myReply;
  684.  
  685.         myTypeList[0] = MovieFileType;
  686.     
  687.         StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
  688.         if (!myReply.sfGood)
  689.             break;    // the user doesn't want any more source movies
  690.     
  691.         // save the FSSpec from the reply information
  692.         gSpecList[mySpecCount] = myReply.sfFile;
  693.         
  694.         mySpecCount++;
  695.     }
  696.     
  697.     QTEffects_DisplayDialogForSources(gSpecList, mySpecCount);
  698. }
  699.